跳到主要内容

Go 中的 Slice

基础概念类问题

1. 什么是切片?切片和数组有什么区别?

答案要点:

  • 切片是动态数组,数组是固定长度的
  • 切片声明时不需要指定长度:[]int{1,2,3},数组需要:[3]int{1,2,3}
  • 切片是引用类型,数组是值类型
  • 切片有长度(len)和容量(cap)两个概念

2. 切片的底层数据结构是什么?

答案要点: 切片本质上是一个结构体,包含三个字段:

type slice struct {
ptr *T // 指向底层数组的指针
len int // 长度
cap int // 容量
}

3. 如何创建切片?有几种方式?

答案要点:

  1. 字面量方式:slice := []int{1, 2, 3}
  2. make函数:slice := make([]int, len, cap)
  3. 从数组或切片截取:slice := arr[start:end]
// 示例代码
languages := []string{"Go", "Python", "C"}
nums := make([]int, 3, 5) // len=3, cap=5
arr := [5]int{1,2,3,4,5}
sub := arr[1:3] // [2, 3]

切片扩容机制

4. 切片的扩容机制是怎样的?

答案要点:

  • 当容量小于1024时,按2倍扩容
  • 当容量大于等于1024时,按1.25倍扩容
  • 扩容时会创建新的底层数组

5. 切片会缩容吗?

答案: 不会。Go的切片不会自动缩容,即使删除元素后,底层数组的容量仍然保持不变。

切片操作与陷阱

6. 下面代码的输出是什么?为什么?

func main() {
nums := make([]int, 0, 8)
nums = append(nums, 1, 2, 3, 4, 5)
nums2 := nums[2:4]
nums2 = append(nums2, 50, 60)
fmt.Println(nums) // ?
fmt.Println(nums2) // ?
}

答案:

[1 2 3 4 50]
[3 4 50 60]

原因: nums和nums2指向同一个底层数组,nums2的append操作修改了底层数组的值。

7. 如何安全地创建子切片避免数据共享?

答案要点:

  1. 使用copy函数深拷贝
  2. 使用三参数切片语法限制容量
// 方法1:copy
b := make([]int, len(a[0:2]))
copy(b, a[0:2])

// 方法2:三参数切片
b := append(a[0:1:1], 3) // a[start:end:cap]

8. 下面代码的输出是什么?

func modify(s []int) {
s = append(s, 1, 2, 3, 4, 5, 6, 7, 8)
s[0] = 200
}

func main() {
a := []int{1, 2}
modify(a)
fmt.Println(a) // ?
}

答案: [1 2]

原因: 虽然切片是引用类型,但切片结构体本身是值传递。当append导致扩容时,新切片指向了不同的底层数组。

性能相关问题

9. 在什么场景下需要预分配切片容量?为什么?

答案要点:

  • 当知道最终切片大小时,应该预分配容量
  • 避免多次扩容导致的内存拷贝
  • 提升性能,减少垃圾回收压力
// 不好的做法
var result []int
for i := 0; i < 10000; i++ {
result = append(result, i) // 多次扩容
}

// 好的做法
result := make([]int, 0, 10000) // 预分配容量
for i := 0; i < 10000; i++ {
result = append(result, i)
}

10. 切片的哪些操作时间复杂度较高?

答案:

  • 头部插入/删除:O(n),需要移动所有元素
  • 中间插入/删除:O(n),需要移动部分元素
  • 尾部追加:O(1),但可能涉及扩容的O(n)操作

内存管理问题

11. 什么情况下切片会导致内存泄漏?如何避免?

答案要点:

  • 大切片截取小片段时,底层大数组无法释放
  • 解决方案:使用copy创建新切片
// 可能导致内存泄漏
func getLastTwo(large []int) []int {
return large[len(large)-2:] // 整个large数组无法释放
}

// 正确做法
func getLastTwo(large []int) []int {
result := make([]int, 2)
copy(result, large[len(large)-2:])
return result
}

12. 数组和切片在函数传参时有什么区别?

答案:

  • 数组是值传递,会完整拷贝整个数组
  • 切片结构体是值传递,但指向同一个底层数组
func modifyArray(arr [3]int) {
arr[0] = 100 // 不会影响原数组
}

func modifySlice(s []int) {
s[0] = 100 // 会影响原切片
}

实际应用问题

13. 如何高效地在切片指定位置插入元素?

func insert(slice []int, index int, value int) []int {
slice = append(slice, 0)
copy(slice[index+1:], slice[index:])
slice[index] = value
return slice
}

14. 如何实现切片的就地过滤?

func filter(slice []int, keep func(int) bool) []int {
n := 0
for _, v := range slice {
if keep(v) {
slice[n] = v
n++
}
}
return slice[:n]
}

15. 切片的零值是什么?如何判断切片是否为空?

答案:

  • 切片的零值是nil
  • 判断方法:if slice == nilif len(slice) == 0
var s []int          // s == nil, len(s) == 0
s2 := make([]int, 0) // s2 != nil, len(s2) == 0
s3 := []int{} // s3 != nil, len(s3) == 0

Reference

go语言的 slice切片不是纯引用类型 The 3 ways to sort in Go 极客兔兔-切片(slice)性能及陷阱 Insert a value in a slice at a given index